package de.uni_stuttgart.ipvs.ids.clocks.lamport;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.util.Set;
import de.uni_stuttgart.ipvs.ids.clocks.Clock;
import de.uni_stuttgart.ipvs.ids.communicationLib.VSDatagramSocket;

/**
 * A TimeSender for the clock synchronization algorithm of Lamport. Sends it's
 * time periodically to its neighbours.
 * 
 * @author Team 3/4 Elkhan Dadashov, Ahmed Labib, Dhaval A Shah, Patrick Stortz
 */
public class TimeSender implements Runnable {

	/** The clock used by this TimeSender */
	private Clock clock;
	/** The VSDatagramSocket used for communication with the neighbours */
	private VSDatagramSocket senderSocket;
	/** The socket addresses of all the neighbours */
	private Set<InetSocketAddress> neighbourSet;
	/** The maximum clock skew allowed in the system */
	private final static long delta = 1000;
	/** The maximum delay of the communication channels in the system */
	private final static long maximum_delay = 250;
	/** The maximum clock drift in the system */
	private final static double maximal_drift_rate = 0.14;
	/** The maximum length of a message of this TimeSender */
	public final static int timeMessageLength = String.valueOf(Long.MIN_VALUE)
			.getBytes().length;

	/**
	 * Creates a new instance of a TimeSender
	 * 
	 * @param clock
	 *            The clock to be used by this TimeSender
	 * @param socket
	 *            The VSDatagramSocket used for communication with the
	 *            neighbours
	 * @param neighbourSet
	 *            The socket addresses of all the neighbours
	 */
	public TimeSender(Clock clock, VSDatagramSocket socket,
			Set<InetSocketAddress> neighbourSet) {
		this.clock = clock;
		this.neighbourSet = neighbourSet;
		this.senderSocket = socket;
	}

	@Override
	public void run() {
		String threadName = "Sender with " + clock.getName();

		// Explanation for Interval:
		// It holds that skew (here 1000) is approx d(2*c*tau+z)
		// for all t >= t0 + tau*d.
		// with max_delay = min_delay + z
		// --> z = max_delay - min_delay (here 250-100=150)
		// d : Diameter of graph (here 2)
		// c : maximum drift rate (here 0.14)
		// tau : interval a (synchronization) message is sent
		// --> skew = 4*c*tau+z --> tau = (skew-z) / (4*c) --> tau =
		// (1000-150)/(4*0.14)=1517.85714
		// all units are in ms!

		// calculate synchronization interval
		long z = TimeSender.maximum_delay - TimeReceiver.minimum_delay;
		long interval = (long) ((delta - z) / (4.0 * maximal_drift_rate));
		// since it is an approximation, set the interval a little bit smaller
		interval = (long) (0.97 * interval);

		// system ready, reset the clock
		this.clock.setTime(0);
		System.out.println(threadName + " | Sender with " + clock.getName()
				+ " is running (sync interval="
				+ Clock.millisecondsToString(interval) + ")...");// DEBUG

		// loop and send the time to neighbours periodically
		while (true) {

			// wait for the defined interval
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				System.err
						.println(threadName
								+ " | Could not wait for the next interval for synchronization. Exception: "
								+ e.getMessage());// DEBUG
				// ignore this interrupt and try to sync our clock
			}

			// create the sync UDP packet
			byte[] syncData = String.valueOf(clock.getTime()).getBytes();
			DatagramPacket syncPacket = new DatagramPacket(syncData,
					syncData.length);

			// loop through all neighbors
			for (InetSocketAddress address : neighbourSet) {

				// set the receiver
				syncPacket.setSocketAddress(address);

				// send the sync UDP packet
				try {
					senderSocket.send(syncPacket);
				} catch (IOException e) {
					System.err.println(threadName
							+ " | Could not send our time to the sender at "
							+ address + ". Exception: " + e.getMessage());// DEBUG
					// ignore this failed sync and try the next neighbour
					continue;
				}
			}
		}
	}

}
